#pragma once

#include <stdint.h>

#include <glog/logging.h>

#include "index/index.h"
#include "util/snapshot_vector/snapshot_vector.h"
#include "file_system/file_writer/normal_file_writer.h"
#include "util/pool/pool.h"
#include "util/status.h"

namespace hawking {
namespace indexlib {

DECLARE_int32(posting_realtime_upadte_space);

class PostingWriter {
public:
    PostingWriter(Pool* pool) :
        doc_ids_(pool_allocator<uint32_t>(pool)),
        realtime_update_(new HashMap<DocId, bool>(FLAGS_posting_realtime_upadte_space)) {}
    ~PostingWriter() = default;

    void AddDocument(uint32_t doc_id) {
        bool* ret = realtime_update_->Find(doc_id);
        if (ret) {
            if (*ret) {
                *ret = false;
            }
            return;
        }

        doc_ids_.PushBack(doc_id);
    }

    void DeleteDocument(DocId doc_id) {
        bool* ret = realtime_update_->Find(doc_id);
        if (ret) {
            if (false == *ret) {
                *ret = true;
            }
            return;
        }

        realtime_update_->Insert(doc_id, true);
    }

    void RemoveDocument(uint32_t doc_id) {
        realtime_update_->FindAndInsert(doc_id, true);
    }

    Status Dump(
        std::shared_ptr<NormalFileWriter> file_writer,
        size_t* offset,
        const std::unordered_map<DocId, DocId>& old2new_map) {
        auto iter = doc_ids_.Snapshot();
        while (iter.HasNext()) {
            DocId cur_doc_id = iter.Next();
            if (old2new_map.find(cur_doc_id) != old2new_map.end()) {
                ReturnValue<size_t> ret = file_writer->Write(
                    (uint8_t*)&cur_doc_id,
                    sizeof(DocId),
                    *offset);
                if (ret.Code() != Status::OK) {
                    return ret.Code();
                }

                *offset += sizeof(DocId);
            }
        }

        return Status::OK;
    }

    // old2new_map is docs after filtered which is deleted during building
    // when dumping, the deleted docs would not write to disk file
    size_t DocListLength(const std::unordered_map<DocId, DocId>& old2new_map) {
        size_t filtered = 0;
        auto iter = doc_ids_.Snapshot();
        while (iter.HasNext()) {
            DocId doc_id = iter.Next();
            bool* ret = realtime_update_->Find(doc_id);
            if (ret && (*ret)) {
                ++filtered;
                continue;
            }

            if (old2new_map.find(doc_id) == old2new_map.end()) {
                ++filtered;
            }
        }

        return doc_ids_.Size() - filtered;
    }

    void CopySnapShot(SegmentContext* segment_ctx) {
        auto iter = doc_ids_.Snapshot();
        segment_ctx->Reserve(iter.Size());
        while (iter.HasNext()) {
            DocId doc_id = iter.Next();
            bool* ret = realtime_update_->Find(doc_id);
            if (!ret || (*ret) == false) {
                segment_ctx->PushDocId();
            }
        }
    }

private:
    SnapshotVector<DocId, pool_allocator<DocId>> doc_ids_;
    std::unique_ptr<HashMap<DocId, bool>> realtime_update_;
};

}
}